// 实现这个项目的构建任务
const { src, dest, parallel, series, watch } = require('gulp');
const del = require('del');
// 后续通过 gulp-load-plugins api 自动引入所需的插件
const loadPlugins = require('gulp-load-plugins');
const browserSync = require('browser-sync');
const eslint = require('gulp-eslint');
const plugins = loadPlugins();
// 启动一个开发服务
const bs = browserSync.create();

// 模板引擎数据
const data = {
  menus: [
    {
      name: 'Home',
      icon: 'aperture',
      link: 'index.html',
    },
    {
      name: 'Features',
      link: 'features.html',
    },
    {
      name: 'About',
      link: 'about.html',
    },
    {
      name: 'Contact',
      link: '#',
      children: [
        {
          name: 'Twitter',
          link: 'https://twitter.com/w_zce',
        },
        {
          name: 'About',
          link: 'https://weibo.com/zceme',
        },
        {
          name: 'divider',
        },
        {
          name: 'About',
          link: 'https://github.com/zce',
        },
      ],
    },
  ],
  pkg: require('./package.json'),
  date: new Date(),
};

// 构建前的清理工作
const clean = () => {
  // []: 存放需要清理的文件路径
  return del([ 'dist', 'temp' ]);
};

// css 样式编译
const style = () => {
  // base 基准路径，保持与转换前的目录结构一致
  return src('src/assets/styles/*.scss', { base: 'src' })
    // outputStyle：指定构建后的样式，expanded --- 完全展开
    .pipe(plugins.sass({ outputStyle: 'expanded' }))
    .pipe(dest('temp'))
    .pipe(bs.reload({ stream: true })); // 文件变更后，以流的方式推向浏览器及时更新
};

// js 编译
const script = () => {
  return src('src/assets/scripts/*.js', { base: 'src' })
    // babel 转换 js；presets: 转换 ES 的新特性，如果不传 presets 导致转换无效
    .pipe(plugins.babel({ presets: [ '@babel/preset-env' ] }))
    .pipe(dest('temp'))
    .pipe(bs.reload({ stream: true }));
};

// html 文件编译
const page = () => {
  return src('src/*.html', { base: 'src' })
    // swig：模板引擎转换插件; data:模板引擎需要的数据
    .pipe(plugins.swig({ data }))
    .pipe(dest('temp'))
    .pipe(bs.reload({ stream: true }));
};

// 图片压缩
const image = () => {
  return src('src/assets/images/**', { base: 'src' })
    // imagemin: 无损压缩图片
    .pipe(plugins.imagemin())
    .pipe(dest('dist'));
};

// 字体文件压缩
const font = () => {
  return src('src/assets/fonts/**', { base: 'src' })
    // imagemin: 对于不能压缩的文件，不会去处理
    .pipe(plugins.imagemin())
    .pipe(dest('dist'));
};

// 额外的文件处理
const extra = () => {
  return src('public/**', { base: 'public' })
    .pipe(dest('dist'));
};

// 启动一个 web 务器
const serve = () => {
  // 监听源文件代码的变更，并执行对应的任务
  watch('src/assets/styles/*.scss', style),
  watch('src/assets/scripts/*.js', script),
  watch('src/*.html', page),
  // 三个文件的变更，执行的任务是相同的，所以可以放到一起啊去监听
  watch([
    'src/assets/fonts/**',
    'src/assets/images/**',
    'public/**',
  ], bs.reload),
  bs.init({
    notify: false,
    // 指定端口
    port: 2021,
    // 监听文件的修改，自动刷新浏览器重新渲染, 这里可以用上面的 reload 代替执行
    // files: 'dist/**',
    server: {
      // 依据数组提供的文件目录，依次查找到对应的文件
      baseDir: [ 'temp', 'src', 'pubilc' ],
      // 处理 node_modules 的引用； 优先级高于 baseDir
      routes: {
        '/node_modules': 'node_modules',
      },
    },
  });
};

// 合并引用相同的文件
const useref = () => {
  return src('temp/*.html', { base: 'temp' })
    // serachPath: 指定要处理的文件目录
    .pipe(plugins.useref({ searchPath: [ 'temp', '.' ] }))
    // 对编译后文件进行压缩处理 if: 判断文件扩展名，采用对用插件对其进行压缩
    .pipe(plugins.if(/\.js$/, plugins.uglify()))
    // html 文件的特殊处理
    .pipe(plugins.if(/\.html$/, plugins.htmlmin({
      // 折叠掉所有的空白字符
      collapseWhitespace: true,
      // 处理 css 的引用格式
      minifyCSS: true,
      // 处理 js 的引用格式
      minifyJS: true,
    })))
    .pipe(plugins.if(/\.css$/, plugins.cleanCss()))
    .pipe(dest('dist'));
};

// esLint 代码格式优化。---这个任务似乎多余的，在配置了 eslint 规则后，修改文件保存代码自动格式化了，不知道 package.json 中为何要暴露这个命令，我就写了一个
const lint = () => {
  return src('src/assets/scripts/*.js', { base: 'src' })
    .pipe(eslint());
};

// 创建组合任务；任务之间没有先后顺序和依赖关系，故可同时执行
const compile = parallel(style, script, page);

/**
 * 上线之前的执行的组合任务 -- 串行任务
 * series: 按需执行，每次build 前限执行文件清理工作；
 * parallel：再执行并行任务
 */
const build = series(
    clean,
    // 并行执行子任务，没有依赖关系
    parallel(
      // 串行 -- 依次执行，有依赖关系
      series(compile, useref),
      image,
      font,
      extra
  )
);

// 开发过程中的执行的构建任务, 依次执行
const start = series(compile, serve);

// 导出常用命令，给开发者使用
module.exports = {
  clean,
  build,
  start,
  serve,
  lint,
};

